بر توصیفگرهای ویژگی پایتون برای ویژگیهای محاسبهشده، اعتبارسنجی ویژگی و طراحی شیءگرای پیشرفته تسلط یابید. با مثالهای عملی و بهترین شیوهها بیاموزید.
توصیفگرهای ویژگی پایتون: ویژگیهای محاسبهشده و منطق اعتبارسنجی
توصیفگرهای ویژگی پایتون یک مکانیزم قدرتمند برای مدیریت دسترسی و رفتار ویژگی در کلاسها ارائه میدهند. آنها به شما اجازه میدهند منطق سفارشی برای دریافت، تنظیم و حذف ویژگیها تعریف کنید، و شما را قادر میسازند تا ویژگیهای محاسبهشده ایجاد کنید، قوانین اعتبارسنجی را اعمال کنید و الگوهای طراحی شیءگرای پیشرفته را پیادهسازی کنید. این راهنمای جامع به بررسی جزئیات توصیفگرهای ویژگی میپردازد، مثالهای عملی و بهترین شیوهها را برای کمک به شما در تسلط بر این ویژگی اساسی پایتون ارائه میدهد.
توصیفگرهای ویژگی چه هستند؟
در پایتون، یک توصیفگر یک ویژگی شیء است که دارای "رفتار بایندی" است، به این معنی که دسترسی به ویژگی آن توسط متدهای موجود در پروتکل توصیفگر لغو شده است. این متدها عبارتند از __get__()
، __set__()
و __delete__()
. اگر هر یک از این متدها برای یک ویژگی تعریف شده باشند، آن ویژگی به یک توصیفگر تبدیل میشود. توصیفگرهای ویژگی، به طور خاص، نوع خاصی از توصیفگر هستند که برای مدیریت دسترسی ویژگی با منطق سفارشی طراحی شدهاند.
توصیفگرها یک مکانیزم سطح پایین هستند که در پشت صحنه توسط بسیاری از ویژگیهای داخلی پایتون، از جمله ویژگیها، متدها، متدهای استاتیک، متدهای کلاس و حتی super()
استفاده میشوند. درک توصیفگرها شما را قادر میسازد تا کد پیچیدهتر و پایتونیکتری بنویسید.
پروتکل توصیفگر
پروتکل توصیفگر متدهایی را تعریف میکند که دسترسی به ویژگی را کنترل میکنند:
__get__(self, instance, owner)
: زمانی فراخوانی میشود که مقدار توصیفگر بازیابی شود.instance
نمونهای از کلاسی است که حاوی توصیفگر است وowner
خود کلاس است. اگر توصیفگر از کلاس قابل دسترسی باشد (به عنوان مثال،MyClass.my_descriptor
)،instance
برابر باNone
خواهد بود.__set__(self, instance, value)
: زمانی فراخوانی میشود که مقدار توصیفگر تنظیم شود.instance
نمونهای از کلاس است وvalue
مقداری است که به آن اختصاص داده میشود.__delete__(self, instance)
: زمانی فراخوانی میشود که ویژگی توصیفگر حذف شود.instance
نمونهای از کلاس است.
برای ایجاد یک توصیفگر ویژگی، باید یک کلاس تعریف کنید که حداقل یکی از این متدها را پیادهسازی کند. بیایید با یک مثال ساده شروع کنیم.
ایجاد یک توصیفگر ویژگی پایه
در اینجا یک مثال ساده از یک توصیفگر ویژگی وجود دارد که یک ویژگی را به حروف بزرگ تبدیل میکند:
class UppercaseDescriptor:
def __get__(self, instance, owner):
if instance is None:
return self # Return the descriptor itself when accessed from the class
return instance._my_attribute.upper() # Access a "private" attribute
def __set__(self, instance, value):
instance._my_attribute = value
class MyClass:
my_attribute = UppercaseDescriptor()
def __init__(self, value):
self._my_attribute = value # Initialize the "private" attribute
# Example usage
obj = MyClass("hello")
print(obj.my_attribute) # Output: HELLO
obj.my_attribute = "world"
print(obj.my_attribute) # Output: WORLD
در این مثال:
UppercaseDescriptor
یک کلاس توصیفگر است که__get__()
و__set__()
را پیادهسازی میکند.MyClass
یک ویژگیmy_attribute
را تعریف میکند که یک نمونه ازUppercaseDescriptor
است.- هنگامی که شما به
obj.my_attribute
دسترسی پیدا میکنید، متد__get__()
ازUppercaseDescriptor
فراخوانی میشود و_my_attribute
زیربنایی را به حروف بزرگ تبدیل میکند. - هنگامی که شما
obj.my_attribute
را تنظیم میکنید، متد__set__()
فراخوانی میشود و_my_attribute
زیربنایی را به روز میکند.
به استفاده از یک ویژگی "خصوصی" (_my_attribute
) توجه کنید. این یک قرارداد رایج در پایتون است که نشان میدهد یک ویژگی برای استفاده داخلی در کلاس در نظر گرفته شده است و نباید مستقیماً از خارج به آن دسترسی پیدا کرد. توصیفگرها مکانیزمی را برای میانجیگری دسترسی به این ویژگیهای "خصوصی" در اختیار ما قرار میدهند.
ویژگیهای محاسبهشده
توصیفگرهای ویژگی برای ایجاد ویژگیهای محاسبهشده عالی هستند - ویژگیهایی که مقادیر آنها به طور پویا بر اساس سایر ویژگیها محاسبه میشوند. این میتواند به ثابت نگه داشتن دادههای شما و نگهداری بهتر کد شما کمک کند. بیایید مثالی را در نظر بگیریم که شامل تبدیل ارز است (با استفاده از نرخهای تبدیل فرضی برای نمایش):
class CurrencyConverter:
def __init__(self, usd_to_eur_rate, usd_to_gbp_rate):
self.usd_to_eur_rate = usd_to_eur_rate
self.usd_to_gbp_rate = usd_to_gbp_rate
class Money:
def __init__(self, usd, converter):
self.usd = usd
self.converter = converter
class EURDescriptor:
def __get__(self, instance, owner):
if instance is None:
return self
return instance.usd * instance.converter.usd_to_eur_rate
def __set__(self, instance, value):
raise AttributeError("Cannot set EUR directly. Set USD instead.")
class GBPDescriptor:
def __get__(self, instance, owner):
if instance is None:
return self
return instance.usd * instance.converter.usd_to_gbp_rate
def __set__(self, instance, value):
raise AttributeError("Cannot set GBP directly. Set USD instead.")
eur = EURDescriptor()
gbp = GBPDescriptor()
# Example usage
converter = CurrencyConverter(0.85, 0.75) # USD to EUR and USD to GBP rates
money = Money(100, converter)
print(f"USD: {money.usd}")
print(f"EUR: {money.eur}")
print(f"GBP: {money.gbp}")
# Attempting to set EUR or GBP will raise an AttributeError
# money.eur = 90 # This will raise an error
در این مثال:
CurrencyConverter
نرخهای تبدیل را در خود نگه میدارد.Money
نشان دهنده مقدار پول بر حسب USD است و به یک نمونهCurrencyConverter
ارجاع دارد.EURDescriptor
وGBPDescriptor
توصیفگرهایی هستند که مقادیر EUR و GBP را بر اساس مقدار USD و نرخهای تبدیل محاسبه میکنند.- ویژگیهای
eur
وgbp
نمونههایی از این توصیفگرها هستند. - متدهای
__set__()
یکAttributeError
را افزایش میدهند تا از تغییر مستقیم مقادیر محاسبهشده EUR و GBP جلوگیری کنند. این تضمین میکند که تغییرات از طریق مقدار USD انجام میشوند و ثبات را حفظ میکنند.
اعتبارسنجی ویژگی
توصیفگرهای ویژگی همچنین میتوانند برای اعمال قوانین اعتبارسنجی بر روی مقادیر ویژگی استفاده شوند. این برای اطمینان از یکپارچگی دادهها و جلوگیری از خطاها بسیار مهم است. بیایید یک توصیفگر ایجاد کنیم که آدرسهای ایمیل را اعتبارسنجی کند. ما اعتبارسنجی را برای مثال ساده نگه میداریم.
import re
class EmailDescriptor:
def __init__(self, attribute_name):
self.attribute_name = attribute_name
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__[self.attribute_name]
def __set__(self, instance, value):
if not self.is_valid_email(value):
raise ValueError(f"Invalid email address: {value}")
instance.__dict__[self.attribute_name] = value
def __delete__(self, instance):
del instance.__dict__[self.attribute_name]
def is_valid_email(self, email):
# Simple email validation (can be improved)
pattern = r"^[\w\.-]+@([\w-]+\.)+[\w-]{2,4}$"
return re.match(pattern, email) is not None
class User:
email = EmailDescriptor("email")
def __init__(self, email):
self.email = email
# Example usage
user = User("test@example.com")
print(user.email)
# Attempting to set an invalid email will raise a ValueError
# user.email = "invalid-email" # This will raise an error
try:
user.email = "invalid-email"
except ValueError as e:
print(e)
در این مثال:
EmailDescriptor
آدرس ایمیل را با استفاده از یک عبارت منظم (is_valid_email
) اعتبارسنجی میکند.- متد
__set__()
بررسی میکند که آیا مقدار یک ایمیل معتبر است یا خیر قبل از اینکه آن را اختصاص دهد. اگر اینطور نیست، یکValueError
ایجاد میکند. - کلاس
User
ازEmailDescriptor
برای مدیریت ویژگیemail
استفاده میکند. - توصیفگر مقدار را مستقیماً در
__dict__
نمونه ذخیره میکند، که امکان دسترسی را بدون فعال کردن دوباره توصیفگر فراهم میکند (جلوگیری از بازگشت بینهایت).
این اطمینان میدهد که فقط آدرسهای ایمیل معتبر میتوانند به ویژگی email
اختصاص داده شوند و یکپارچگی دادهها را افزایش میدهند. توجه داشته باشید که تابع is_valid_email
فقط اعتبارسنجی اساسی را ارائه میدهد و میتواند برای بررسیهای قویتر بهبود یابد، احتمالاً با استفاده از کتابخانههای خارجی برای اعتبارسنجی ایمیل بینالمللی در صورت نیاز.
استفاده از تابع داخلی `property`
پایتون یک تابع داخلی به نام property()
ارائه میدهد که ایجاد توصیفگرهای ویژگی ساده را آسان میکند. این اساساً یک پوشش راحتی در اطراف پروتکل توصیفگر است. این اغلب برای ویژگیهای محاسبهشده اساسی ترجیح داده میشود.
class Rectangle:
def __init__(self, width, height):
self._width = width
self._height = height
def get_area(self):
return self._width * self._height
def set_area(self, area):
# Implement logic to calculate width/height from area
# For simplicity, we'll just set width and height to the square root
import math
side = math.sqrt(area)
self._width = side
self._height = side
def delete_area(self):
self._width = 0
self._height = 0
area = property(get_area, set_area, delete_area, "The area of the rectangle")
# Example usage
rect = Rectangle(5, 10)
print(rect.area) # Output: 50
rect.area = 100
print(rect._width) # Output: 10.0
print(rect._height) # Output: 10.0
del rect.area
print(rect._width) # Output: 0
print(rect._height) # Output: 0
در این مثال:
property()
حداکثر چهار آرگومان میگیرد:fget
(دریافتکننده)،fset
(تنظیمکننده)،fdel
(حذفکننده) وdoc
(docstring).- ما متدهای جداگانهای را برای دریافت، تنظیم و حذف
area
تعریف میکنیم. property()
یک توصیفگر ویژگی ایجاد میکند که از این متدها برای مدیریت دسترسی به ویژگی استفاده میکند.
تابع داخلی property
اغلب برای موارد ساده خواناتر و مختصرتر از ایجاد یک کلاس توصیفگر جداگانه است. با این حال، برای منطق پیچیدهتر یا زمانی که نیاز به استفاده مجدد از منطق توصیفگر در چندین ویژگی یا کلاس دارید، ایجاد یک کلاس توصیفگر سفارشی سازماندهی و قابلیت استفاده مجدد بهتری را فراهم میکند.
چه زمانی از توصیفگرهای ویژگی استفاده کنیم
توصیفگرهای ویژگی یک ابزار قدرتمند هستند، اما باید با احتیاط از آنها استفاده کرد. در اینجا برخی از سناریوهایی که در آن بسیار مفید هستند آورده شده است:
- ویژگیهای محاسبهشده: زمانی که مقدار یک ویژگی به سایر ویژگیها یا عوامل خارجی بستگی دارد و باید به طور پویا محاسبه شود.
- اعتبارسنجی ویژگی: زمانی که نیاز دارید قوانین یا محدودیتهای خاصی را بر روی مقادیر ویژگی اعمال کنید تا یکپارچگی دادهها حفظ شود.
- کپسولهسازی داده: زمانی که میخواهید نحوه دسترسی و اصلاح ویژگیها را کنترل کنید و جزئیات پیادهسازی زیربنایی را پنهان کنید.
- ویژگیهای فقط خواندنی: زمانی که میخواهید از اصلاح یک ویژگی پس از مقداردهی اولیه آن جلوگیری کنید (فقط با تعریف یک متد
__get__
). - بارگذاری تنبل: زمانی که میخواهید مقدار یک ویژگی را فقط زمانی بارگیری کنید که برای اولین بار به آن دسترسی پیدا کردید (به عنوان مثال، بارگیری دادهها از یک پایگاه داده).
- ادغام با سیستمهای خارجی: توصیفگرها میتوانند به عنوان یک لایه انتزاعی بین شیء شما و یک سیستم خارجی مانند پایگاه داده/API استفاده شوند، بنابراین برنامه شما نیازی به نگرانی در مورد نمایش زیربنایی ندارد. این قابلیت انتقال برنامه شما را افزایش میدهد. تصور کنید که یک ویژگی دارید که یک تاریخ را ذخیره میکند، اما ذخیرهسازی زیربنایی ممکن است بر اساس پلتفرم متفاوت باشد، میتوانید از یک توصیفگر برای انتزاع این موضوع استفاده کنید.
با این حال، از استفاده غیرضروری از توصیفگرهای ویژگی خودداری کنید، زیرا میتوانند پیچیدگی را به کد شما اضافه کنند. برای دسترسی ساده به ویژگی بدون هیچ منطق خاصی، دسترسی مستقیم به ویژگی اغلب کافی است. استفاده بیش از حد از توصیفگرها میتواند درک و نگهداری کد شما را دشوارتر کند.
بهترین شیوهها
در اینجا برخی از بهترین شیوهها برای در نظر گرفتن هنگام کار با توصیفگرهای ویژگی آورده شده است:
- از ویژگیهای "خصوصی" استفاده کنید: دادههای زیربنایی را در ویژگیهای "خصوصی" (به عنوان مثال،
_my_attribute
) ذخیره کنید تا از تداخل نامها جلوگیری کنید و از دسترسی مستقیم از خارج از کلاس جلوگیری کنید. - رسیدگی به
instance is None
: در متد__get__()
، موردی را که در آنinstance
برابر باNone
است، رسیدگی کنید، که زمانی رخ میدهد که توصیفگر از خود کلاس و نه یک نمونه قابل دسترسی باشد. در این حالت خود شیء توصیفگر را برگردانید. - ایجاد استثنائات مناسب: هنگامی که اعتبارسنجی ناموفق است یا زمانی که اجازه تنظیم یک ویژگی داده نمیشود، استثنائات مناسب (به عنوان مثال،
ValueError
،TypeError
،AttributeError
) ایجاد کنید. - توصیفگرهای خود را مستند کنید: به کلاسها و ویژگیهای توصیفگر خود docstring اضافه کنید تا هدف و استفاده آنها را توضیح دهید.
- عملکرد را در نظر بگیرید: منطق توصیفگر پیچیده میتواند بر عملکرد تأثیر بگذارد. کد خود را پروفایل کنید تا هرگونه گلوگاه عملکرد را شناسایی کنید و توصیفگرهای خود را بر این اساس بهینه کنید.
- رویکرد مناسب را انتخاب کنید: بر اساس پیچیدگی منطق و نیاز به قابلیت استفاده مجدد، تصمیم بگیرید که از تابع داخلی
property
یا یک کلاس توصیفگر سفارشی استفاده کنید. - ساده نگه دارید: درست مانند هر کد دیگری، باید از پیچیدگی اجتناب شود. توصیفگرها باید کیفیت طراحی شما را بهبود بخشند، نه اینکه آن را مبهم کنند.
تکنیکهای پیشرفته توصیفگر
فراتر از اصول اولیه، توصیفگرهای ویژگی میتوانند برای تکنیکهای پیشرفتهتر استفاده شوند:
- توصیفگرهای غیر دادهای: توصیفگرهایی که فقط متد
__get__()
را تعریف میکنند، توصیفگرهای غیر دادهای (یا گاهی اوقات توصیفگرهای "سایهدار") نامیده میشوند. آنها اولویت کمتری نسبت به ویژگیهای نمونه دارند. اگر یک ویژگی نمونه با همان نام وجود داشته باشد، توصیفگر غیر دادهای را سایه میاندازد. این میتواند برای ارائه مقادیر پیشفرض یا رفتار بارگذاری تنبل مفید باشد. - توصیفگرهای دادهای: توصیفگرهایی که
__set__()
یا__delete__()
را تعریف میکنند، توصیفگرهای دادهای نامیده میشوند. آنها اولویت بیشتری نسبت به ویژگیهای نمونه دارند. دسترسی یا اختصاص به ویژگی همیشه متدهای توصیفگر را فعال میکند. - ترکیب توصیفگرها: میتوانید چندین توصیفگر را برای ایجاد رفتار پیچیدهتر ترکیب کنید. به عنوان مثال، میتوانید یک توصیفگر داشته باشید که هم ویژگی را اعتبارسنجی و هم تبدیل میکند.
- فرا کلاسها: توصیفگرها به طور قدرتمندی با فرا کلاسها تعامل دارند، جایی که ویژگیها توسط فرا کلاس اختصاص داده میشوند و توسط کلاسهایی که ایجاد میکند به ارث میرسند. این امکان طراحی فوقالعاده قدرتمندی را فراهم میکند، توصیفگرها را در سراسر کلاسها قابل استفاده مجدد میکند و حتی اختصاص توصیفگر را بر اساس فراداده خودکار میکند.
ملاحظات جهانی
هنگام طراحی با توصیفگرهای ویژگی، به ویژه در یک زمینه جهانی، موارد زیر را در نظر داشته باشید:
- محلیسازی: اگر دادههایی را تأیید میکنید که به محلی بستگی دارد (به عنوان مثال، کدهای پستی، شماره تلفن)، از کتابخانههای مناسبی استفاده کنید که از مناطق و قالبهای مختلف پشتیبانی میکنند.
- مناطق زمانی: هنگام کار با تاریخها و زمانها، مراقب مناطق زمانی باشید و از کتابخانههایی مانند
pytz
برای رسیدگی صحیح به تبدیلها استفاده کنید. - ارز: اگر با مقادیر ارزی سر و کار دارید، از کتابخانههایی استفاده کنید که از ارزها و نرخهای ارز مختلف پشتیبانی میکنند. استفاده از یک قالب ارز استاندارد را در نظر بگیرید.
- رمزگذاری کاراکتر: اطمینان حاصل کنید که کد شما به درستی از رمزگذاریهای کاراکتر مختلف، به ویژه هنگام تأیید رشتهها، پشتیبانی میکند.
- استانداردهای اعتبارسنجی داده: برخی از مناطق دارای الزامات اعتبارسنجی داده قانونی یا نظارتی خاصی هستند. از این موارد آگاه باشید و اطمینان حاصل کنید که توصیفگرهای شما با آنها مطابقت دارند.
- دسترسی: ویژگیها باید به گونهای طراحی شوند که به برنامه شما اجازه دهند بدون تغییر در طراحی اصلی، با زبانها و فرهنگهای مختلف سازگار شوند.
نتیجهگیری
توصیفگرهای ویژگی پایتون یک ابزار قدرتمند و همهکاره برای مدیریت دسترسی و رفتار ویژگی هستند. آنها به شما اجازه میدهند ویژگیهای محاسبهشده ایجاد کنید، قوانین اعتبارسنجی را اعمال کنید و الگوهای طراحی شیءگرای پیشرفته را پیادهسازی کنید. با درک پروتکل توصیفگر و پیروی از بهترین شیوهها، میتوانید کد پایتون پیچیدهتر و قابل نگهداریتری بنویسید.
از اطمینان از یکپارچگی دادهها با اعتبارسنجی گرفته تا محاسبه مقادیر مشتقشده بر اساس تقاضا، توصیفگرهای ویژگی راهی ظریف برای سفارشیسازی مدیریت ویژگی در کلاسهای پایتون شما ارائه میدهند. تسلط بر این ویژگی درک عمیقتری از مدل شیء پایتون را باز میکند و شما را قادر میسازد تا برنامههای کاربردی قویتر و انعطافپذیرتری بسازید.
با استفاده از property
یا توصیفگرهای سفارشی، میتوانید به طور قابل توجهی مهارتهای پایتون خود را بهبود بخشید.